
const defer = typeof Promise=='function' ? Promise.prototype.then.bind(Promise.resolve()) : setTimeout;

export default class State {

	constructor(initState) {
		this.stateData = initState;
		this.stateNode = this.createStateNode(initState);
	}


	createStateNode(initState) {
		let node = { _keep: [] }, keys = Object.keys(initState);
		keys.forEach(key => {
			let obj = initState[key];
			if (!isPlainObject(obj)) {
				node[key] = [];
			} else {
				node[key] = this.createStateNode(obj);
			}
		});
		return node;
	}

	setState(state) {
		if (this.destroyed) return;
		this._setState(state, this.stateData);
		defer(Object.assign(this.stateData, state));
	}

	_setState(stateVal, stateData) {
		let keys = Object.keys(stateVal);
		if (keys.length === 0) return;
		keys.forEach(key => {
			let obj = stateVal[key], preObj = stateData[key];
			let node = this.stateNode[key];
			if (preObj === undefined) {
				if (!isPlainObject(obj)) {
					this.stateNode[key] = [];
				} else {
					this.stateNode[key] = this.createStateNode(obj);
				}
			} else {
				let callbacks = node._keep || node;
				if (/*obj !== preObj && */callbacks.length) {
					callbacks.forEach(cb => cb(obj, preObj));
				}
			}
			if (isPlainObject(obj)) {
				this._setState(obj, preObj);
			}
		});
	}

	// mapStateToProps(state) { }
	// mapDispatchToProps(dispatch) { }

	bindState(key, listener) {
		const paths = key.split('.');
		const node = paths.reduce((s, v) => s[v], this.stateNode);
		const val = paths.reduce((s, v) => s[v], this.stateData);
		if (node == undefined) {
			throw new Error(`${key} uninitialized `);
		}
		const callbacks = node._keep || node;

		callbacks.push(listener);
		listener(val, null);

		return function unbind() {
			for (let i = 0; i < callbacks.length; ++i) {
				if (callbacks[i] === listener) {
					callbacks.splice(i);
					break;
				}
			}
		};
	}

	bindAsyncState(key, listener) {

	}

	useState(val) {
		return this.useReducer(invokeOrReturn, initialState);
	}

	useReducer(reducer, initialState, init) {
		return [
			init == null ? invokeOrReturn(null, initialState) : init(initialState),

			action => {
				const nextValue = reducer(this.stateData, action);
				if (this.stateData !== nextValue) {
					this.setState(nextValue);
				}
			}
		];
	}
	
	destroy() {
		this.stateData = null;
		this.stateNode = null;
		this.destroyed = true;
	}

	static useMemo(data, key, context) {

	}


}

function invokeOrReturn(arg, f) {
	return typeof f === 'function' ? f(arg) : f;
}


function isPlainObject(value) {
	if (!value || typeof value !== 'object' || ({}).toString.call(value) != '[object Object]') {
		return false;
	}
	var proto = Object.getPrototypeOf(value);
	if (proto === null) {
		return true;
	}
	var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
	return typeof Ctor == 'function' && Ctor instanceof Ctor && Function.prototype.toString.call(Ctor) === Function.prototype.toString.call(Object);
}
